#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/cma.h>
#include <linux/mm.h>
#include <linux/of.h>
/* #include <linux/dma-mapping.h> */
#include <linux/dma-contiguous.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
/* #include <linux/sizes.h> */
#include <linux/types.h>
#include <asm/uaccess.h>
 
#define CMA_NODE 	"/video_cma@69000000"
#define CMA_NAME 	"video_cma@69000000"

struct cma_allocation {
	struct list_head list;
	struct page *cma_page;
	int count;
	unsigned long vaddr;
};
 
static struct device *cma_dev;
static LIST_HEAD(cma_allocations);
/* static DEFINE_SPINLOCK(cma_lock); */

/*
 * struct cma *find_cma_by_name(const char *name) {
 *     int idx;
 *     for (idx = 0; idx < MAX_CMA_AREAS; idx++) {
 *         if (!strcmp(name, cma_areas[idx].name))
 *             return &cma_areas[idx];
 *     }
 *     return NULL;
 * }
 */

/*
* any read request will free the 1st allocated coherent memory,
* eg, cat /dev/cma_test
*/
static ssize_t
cma_test_read(struct file * file, char __user *buf, size_t count, loff_t *ppos)
{
	struct cma_allocation *alloc = NULL;
	bool ret;
	/* spin_lock(&cma_lock); */
	if(!list_empty(&cma_allocations)) {
		alloc = list_first_entry(&cma_allocations, struct cma_allocation, list);
		list_del(&alloc->list);
	}
    
    /* spin_unlock(&cma_lock); */
    
   	if(!alloc)
   		return -EIDRM;
   
    /* dma_free_coherent(cma_dev, alloc->size, alloc->virt, alloc->dma); */
	ret = dma_release_from_contiguous(cma_dev, alloc->cma_page, alloc->count);
	/* ret = cma_release(dev_get_cma_area(cma_dev), alloc->cma_page, alloc->count); */
    if (ret)
		_dev_info(cma_dev, "free %d pages. vaddr: 0x%lx paddr: 0x%x\n",
   			alloc->count, alloc->vaddr, __pa(alloc->vaddr));
   	kfree(alloc);
   	return 0;
}
 
 
/*
* any write request will alloc a new coherent memory, eg,
* echo 1024 > /dev/cma_test
* will request 1024KiB by CMA
*/
 
static ssize_t 
cma_test_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
	struct cma_allocation *alloc;
	alloc = kmalloc(sizeof(struct cma_allocation), GFP_KERNEL);
	if(!alloc)
		return -ENOMEM;

	if(copy_from_user(&alloc->count, buf, count)!= 0)
		return -EFAULT;
	/* alloc->virt = dma_alloc_coherent(cma_dev, alloc->size,
					&alloc->dma, GFP_KERNEL); */
	alloc->cma_page = dma_alloc_from_contiguous(cma_dev, alloc->count, 8);
	if (!alloc->cma_page) {
		pr_notice("alloc cma pages failed!\n");
		return -EFAULT;
	}
	/* alloc->cma_page = cma_alloc(dev_get_cma_area(cma_dev), alloc->count, 8); */
	alloc->vaddr = (unsigned long)page_address(alloc->cma_page);
 
	if(alloc->vaddr) {
		_dev_info(cma_dev, "alloc %d pages, vaddr: 0x%lx paddr: 0x%x \n",
			alloc->count, alloc->vaddr, __pa(alloc->vaddr));

		/* spin_lock(&cma_lock); */
		list_add_tail(&alloc->list, &cma_allocations);
		/* spin_unlock(&cma_lock); */
	
		return 0;
	}
	else {
		dev_err(cma_dev, "no mem in CMA area\n");
		kfree(alloc);
		return -ENOSPC;
	}
}
 
static const struct file_operations cma_test_fops = {
		.owner = THIS_MODULE,
		.read  = cma_test_read,
		.write = cma_test_write,
};
 
static struct miscdevice cma_test_misc = {
		.name = "cma_test",
		.fops = &cma_test_fops,
};
 
 
static int __init cma_test_init(void)
{
	struct device_node *np = NULL;
	int ret;
	/* const char *cma_name; */
	cma_dev = kmalloc(sizeof(struct device), GFP_KERNEL);
	if (!cma_dev) {
		pr_notice("cma_dev alloc failed! \n");
		goto err;
	}

	np = of_find_node_by_path("/reserved-memory/video_cma@69000000");
	if (!np) {
		pr_notice("find node %s failed!\n", CMA_NAME);
		goto err;
	}
	pr_notice("find node %s ok!\n", CMA_NAME);
	cma_dev->cma_area = find_cma_by_name(CMA_NAME);
	if (cma_dev->cma_area)
		pr_notice("find cma success!\n");
	else {
		pr_notice("find cma err!\n");
		goto err;
	}
	ret = misc_register(&cma_test_misc);
	if(unlikely(ret)) {
		pr_err("failed to register cma test misc device!\n");
		goto err;
	}
	
	cma_dev = cma_test_misc.this_device;
	cma_dev->coherent_dma_mask = ~0;
	_dev_info(cma_dev, "registered.\n");
	return 0;

err:
	return -EFAULT;
}
 
 
static void __exit cma_test_exit(void)
{
	misc_deregister(&cma_test_misc);
}
 
module_init(cma_test_init);
module_exit(cma_test_exit);
MODULE_LICENSE("GPL");